# Разработка на Java. Практика (Переобучение ЦФТ, 2-й этап)

## Задание №3. Многопоточность

Задание направлено на отработку навыка работы с многопоточностью путем разработки структуры данных для многопоточного окружения. К решению должны быть приложены модульные тесты на все значимые модули.

В данном задании следует развить решение, полученное в результате разработки утилитного метода `cache`, которое кэширует вызов методов, помеченных аннотацией `@Cache`.

Внесите в решение следующие изменения:

1) Кэшируется результат вызова метода не только для текущего состояния объекта, но и для всех предыдущих состояний.

2) Для кэшированных значений указывается время жизни. Если за время жизни значение не было востребовано ни разу, то оно удаляется из кэша. При востребовании значения из кэша, его срок жизни обновляется.

Разберем порядок работа этой конструкции на примере. Мы имеем интерфейс `Fractionable` и реализующий его класс `Fraction`. Обратите внимание, что два его метода помечено аннотацией `@Mutator`, и один аннотацией `@Cache(1000)`. Параметр аннотации `Cache` задает срок жизни объектов в кэше. В данном случае это 1000 миллисекунд.
```Java
    public interface Fractionable{
        double doubleValue();
        void setNum(int num) ;
        void setDenum(int denum) ;
    }

    public class Fraction implements Fractionable {
        private int num;
        private int denum;
    
        public Fraction(int num, int denum) {
            this.num = num;
            this.denum = denum;
        }
     
        @Mutator
        public void setNum(int num) {
            this.num = num;
        }
    
        @Mutator
        public void setDenum(int denum) {
            this.denum = denum;
        }
    
        @Override
        @Cache(1000)
        public double doubleValue() {
            System.out.println("invoke double value");
            return (double) num/denum;
        }
    }
```
Далее, протестируем работу кэширующего объекта.
```Java
    public static void main(String... args) throws Exception {
        Fraction fr= new Fraction(2,3);
        Fractionable num =Utils.cache(fr);
        num.doubleValue();// sout сработал
        num.doubleValue();// sout молчит
    
        num.setNum(5);
        num.doubleValue();// sout сработал
        num.doubleValue();// sout молчит
    
        num.setNum(2);
        num.doubleValue();// sout молчит
        num.doubleValue();// sout молчит
    
        Thread.sleep(1500);
        num.doubleValue();// sout сработал
        num.doubleValue();// sout молчит
    }
```
В первой паре значений: первый вызов обычный, второй дает значение из кэша.

Вторая пара значений вызывается после смены состояния объекта, и следовательно первый вызов обычный, второй кэшированный.

Третья пара вызывается после того, как объект вернулся к состоянию, в котором он был ранее, поэтому возвращается в обоих случаях кэшированная версия.

Последняя пара вызывается спустя 1500 миллисекунд после последнего обращения к кэшу вообще, следовательно срок жизни объектов в кэше закончился, и необходимо пересчитывать кэшированное значение заново. Таким образом первый раз вызывается обычный метод, второй раз возвращается обновленный кэш.

Обеспечьте регулярную очистку кэша от старых значений таким образом, чтобы процесс очистки оказывал наименьшее возможное влияние на время записи и чтения объектов из кэша. В комментариях к решению укажите принцип выбора момента очистки и мотивацию к нему. 